/*
 * GeoVISTA Center (Penn State, Dept. of Geography)
 * Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
 * Licensed under Simplified BSD License
 *
 * As mouse drag, it will select observations. This is to support the selecting operation
 *  
 * totalSelection is to keep selection for the entire application.
    - internally ( for mouse select), computeSelection() will compute the final selection and update the totalSelection
    - externally, u can either set totalSelection, or add/ substract to a value on top of totalSelection.
 * @author: jin Chen 
 * @date: Nov 17, 2005$
 * @version: 1.0
 */
package edu.psu.geovista.app.gmatrix.toolkit;

import epoviz.trex.common.swing.frame.tool.BasicMouseTool;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

public class DfMouseSelectSupport extends BasicMouseTool implements MouseSelectSupport {

    private static final Logger logger=Logger.getLogger(DfMouseSelectSupport.class.getName() );


    //state
    private int policy=POLICY_ONETIME;
    //private Set lastSelection=new HashSet(0);  // 
    private Set curSelection=new HashSet(0);  //selection of current brushing session
    private  Set totalSelection =new HashSet(0); //total selection
    private  Set tempSelection=new HashSet(0);//for calculation purpose.
    boolean toolOn=false;
    private boolean needClearSelection=false;
    MouseSelectTarget target;


    Point initp=new Point();

    public void setTarget(MouseSelectTarget target) {
        JComponent component = ((JComponent) target);
        component.addMouseListener(this);
        component.addMouseMotionListener(this);
        this.target = target;

    }
     public void removeTarget(MouseSelectTarget target) {
        JComponent component = ((JComponent) target);
        component.removeMouseListener(this);
        component.removeMouseMotionListener(this);
        this.target = null;

    }


    public void mousePressed(MouseEvent e) {

        int button = e.getButton();
        logger.finest("click button:"+button);
        if(e.isShiftDown() ){//add another selection
             logger.finer("shiftDown");
        }
        else if (e.isAltDown() ){//substract selection
             logger.finer("altDown");
        }
        else if (e.isControlDown()){
             logger.finer("controlDown");
        }
        else if(SwingUtilities.isLeftMouseButton(e)){//  
            //boolean leftMouseButton = SwingUtilities.isLeftMouseButton(e);
            //button==MouseEvent.BUTTON1 || button ==MouseEvent.NOBUTTON


            setNeedClearSelection(true); // 
            this.setToolOn(true);


        }
        this.updateLCpoint(e.getPoint()) ;
        this.initp.setLocation(e.getX(),  e.getY() );
    }




    public void mouseDragged(MouseEvent e) {
       this.updateLCpoint(e.getPoint()) ;

       if(this.getPolicy()==POLICY_ONETIME){
           return;// 
       }
       else if (this.getPolicy() ==POLICY_INCREMENTAL){

           process(e);

           Set totalSelection = this.getTotalSelection();
           Set curSelection = this.getCurSelection();
           target.mouseSelect(totalSelection , curSelection, false);
       }
       else{
           logger.warning(DfMouseSelectSupport.class.getName() +".mouseDragged() process unknown policy! ");
       }

    }



    public void mouseReleased(MouseEvent e) {

       if(e.getButton()==MouseEvent.BUTTON1){//left clicking
           super.mouseReleased(e);
           process(e);
           target.mouseSelect(this.getTotalSelection(), null, true);  //not have curSelect --> null
           getCurSelection().clear();// 
          // finalSelection.clear();// 
       }
   }

    /**
     * calculate current selection
     * @param e
     */
    protected  void process(MouseEvent e) {
          Set curSelect = target.findMouseSelection(this.initp, lastp, cp);  // 
          // 
          if (curSelect.size() ==0||curSelect==null) {
              getCurSelection().clear();
              return;//for invalid selection, do nothing
          }
          else{
              setCurSelection(curSelect);
          }
          logger.finer("current selection  "+curSelect.size()+" . ");
          if(this.isNeedClearSelection()){ //it happen only on first dragging
              logger.fine("clear last selection to make new select sesssion ");
              // 
               //  
               // 
               //clear selection of previous brushing
               this.clearSelection() ;
               this.target.mouseClearSelection();
              this.setNeedClearSelection(false);
          }
          this.computeSelection(curSelect,e);

      }


    /**
     * based on keyboard input (e.g.: shift, alt, control....), compute final selection
     * @param curSelect
     */
    protected  void computeSelection(Set curSelect, MouseEvent e){
        if(this.isAddSelection(e) ){
             logger.fine("Before Add Selecting  curSel:"+curSelect.size() );
             logger.fine("Before Add Selecting  finSel:"+getTotalSelection().size() );

            this.totalSelection.addAll(curSelect);
            logger.fine("After Add Selecting finSel:"+getTotalSelection().size() );

        }
        else if (isMinusSelection(e)){

            logger.fine("Before Substract Selecting: curSel:"+curSelect.size() );
            logger.fine("Before Substract Selecting finSel:"+getTotalSelection().size() );
            this.totalSelection.removeAll(curSelect);
            logger.fine("After substractSelecting result:"+getTotalSelection().size() );
        }
        else if ( isIntersectSelection(e)){

             logger.fine("Before InterSection Selecting: curSel:"+curSelect.size() );
             logger.fine("Before InterSection Selecting finSel:"+getTotalSelection().size() );
             if(this.getPolicy() ==POLICY_ONETIME){
                 this.totalSelection.retainAll(curSelect);
             }
             else{
                 logger.warning("NOT support incremental intersect selection.");
                 //Need introduce variable lastTotalSelection
                 setSelection(this.getTotalSelection(),this.tempSelection );
                 tempSelection.retainAll(curSelection);
                 if(tempSelection.size() >0){ //only update total when intersect >0
                      setSelection(this.tempSelection,this.getTotalSelection() );
                 }
                 else{
                      logger.fine("No intersection found, remain previous total selection ");
                 }
             }
             //getTotalSelection().retainAll(curSelect);
             logger.fine("After InterSection result:"+getTotalSelection().size() );

        }
        else{

            setTotalSelection(curSelect);//normal select
            logger.fine("Finish normal Selecting result:"+getTotalSelection().size() );
        }


    }

    /**
     * clear current total selection
     */
    public void clearSelection(){
         logger.fine("clear total selection");
         this.totalSelection.clear();
         //getTotalSelection().clear();
    }

    /**
     * extend the method to define ur policy
     * @param e
     * @return
     */
    public boolean isAddSelection(MouseEvent e){
         if(e.isShiftDown() ){//add another selection
             logger.finer("shift key down down, do addSelect");
             return true;
        }
        return false;
    }

    public boolean isMinusSelection(MouseEvent e){
        if (e.isAltDown() ){//substract selection
             logger.finer("alt key down, do minus selection");
        return true;
        }
        return false;
    }

    public boolean isIntersectSelection(MouseEvent e){
         if (e.isControlDown() ){//intersection selection
             logger.finer("control key down, do intersect selection");
        return true;
        }
        return false;
    }



    /*public Set getCopyOfLastSelection() {
        return (Set)((HashSet)lastSelection).clone() ;//shallow copy. Element are Integer type, don't need copy
    }
    public Set getLastSelection() {
        return lastSelection ;//shallow copy. Element are Integer type, don't need copy
    }

    public void setLastSelection(Set lastSel) {
      lastSelection.clear();
      lastSelection.addAll(lastSel) ;
        // Since elements is type of Integer, shallow copy is enough
    }*/
    public boolean isNeedClearSelection() {
        return needClearSelection;
    }

    public void setNeedClearSelection(boolean needClearSelection) {
        this.needClearSelection = needClearSelection;
    }

     public boolean isToolOn() {
        return toolOn;
    }

    public void setToolOn(boolean toolOn) {
        this.toolOn = toolOn;
    }

    public int getPolicy() {
        return policy;
    }

    public void setPolicy(int policy) {
        this.policy = policy;
    }

    public Set getCurSelection() {
        return curSelection;
    }

    public void setCurSelection(Set curSelect) {
        //Collections.
        curSelection.clear();
        curSelection.addAll(curSelect);
        //this.curSelection = curSelection;
    }

    public Set getTotalSelection() {
        //Set copy = (Set) ((HashSet) totalSelection).clone();   //not work
        return totalSelection;
    }

    /**
     *
     * For set totalSelection internally as well as externally
     * @param totalSelect
     */
    public void setTotalSelection(Set totalSelect) {
        
        /**
         * client may getTotalSelection() and pass it into this method.
         * temp will keep the values for  totalSelect. In next step, even totalSelect is clear, u still keep the
         */
        Set temp=new HashSet(totalSelect.size() );
        temp.addAll(totalSelect);

        totalSelection.clear();
        totalSelection.addAll(temp); // 

        logger.fine(" setTotalSelection:"+getTotalSelection().size() );
        if(getTotalSelection().size() ==0){
            System.out.println("");
        }
        //this.totalSelection = totalSelection;
    }

    /**
     * usually request from external to add selection
     * @param select
     */
    public void addSelection(Set select){
         logger.fine(" add to TotalSelection:"+select.size() );
        totalSelection.addAll(select);
    }
     /**
     * usually request from external to substract selection
     * @param select
     */
    public void substractSelection(Set select){
        logger.fine(" substract to TotalSelection:"+select.size() );
        totalSelection.removeAll(select) ;
    }
    public static void setSelection (Set src, Set dest){
        dest.clear();
        dest.addAll(src);
    }
   
}
